package database

import ast.expr.SqlIdentifierExpr
import ast.statement.upsert.SqlUpsert
import expr.*
import query.Page
import query.ddl.CreateIndex
import query.ddl.CreateTable
import query.ddl.DropIndex
import query.ddl.DropTable
import query.delete.Delete
import query.insert.Insert
import query.insert.NativeInsert
import query.select.NativeSelect
import query.select.Select
import query.select.ValuesSelect
import query.select.WithSelect
import query.truncate.Truncate
import query.update.Update
import util.toSqlString
import visitor.checkOLAP
import visitor.getExpr
import visitor.getQueryExpr
import java.sql.SQLException
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.declaredMemberProperties

abstract class DataBaseImpl : DataBase {
    open fun select(vararg columns: String): Select {
        val select = Select(db, getConnection(), isTransaction)
        select.select(*columns)
        return select
    }

    open infix fun select(query: Query): Select {
        val select = Select(db, getConnection(), isTransaction)
        select.invoke(query)
        return select
    }

    open infix fun select(query: List<Query>): Select {
        val select = Select(db, getConnection(), isTransaction)
        select.invoke(query)
        return select
    }

    open fun select(vararg query: Query): Select {
        val select = Select(db, getConnection(), isTransaction)
        select.select(*query)
        return select
    }

    open fun select(): Select {
        return Select(db, getConnection(), isTransaction)
    }

    open fun nativeSelect(sql: String, vararg arg: Any): NativeSelect {
        val argList = arg.map { getExpr(it).toString() }
        var nativeSql = sql
        if (sql.contains("?")) {
            argList.forEach {
                nativeSql = nativeSql.replaceFirst("?", it)
            }
        }
        return NativeSelect(db, nativeSql, getConnection(), isTransaction)
    }

    open fun with(): WithSelect {
        return WithSelect(db, getConnection(), isTransaction)
    }

    open fun values(vararg value: List<Any>) : ValuesSelect {
        return ValuesSelect(db, getConnection(), isTransaction)
    }

    inline fun <reified T> find(primaryKey: Any): T? {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")

        val tableName = (companion as TableSchema).tableName

        val companionClass = companion::class
        val pkCols = companionClass.declaredMemberProperties
            .map { it.getter.call(companion) }
            .filterIsInstance<QueryTableColumn>()
            .filter { it.primaryKey }

        if (pkCols.isEmpty()) {
            throw Exception("实体类的伴生对象中没有设置主键字段")
        }

        if (pkCols.size > 1) {
            throw SQLException("主键字段数量和传入的参数不匹配")
        }

        val pkCol = pkCols[0]

        return select().from(tableName).where(pkCol eq primaryKey).find()
    }

    inline fun <reified T> find(primaryKeys: List<Pair<String, Any>>): T? {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")

        val tableName = (companion as TableSchema).tableName

        val companionClass = companion::class
        val pkCols = companionClass.declaredMemberProperties
            .map { it.name to it.getter.call(companion) }
            .filter { it.second is QueryTableColumn }
            .map { it.first to it.second as QueryTableColumn }
            .filter { it.second.primaryKey }
            .toMap()

        if (pkCols.isEmpty()) {
            throw Exception("实体类的伴生对象中没有设置主键字段")
        }

        if (pkCols.size != primaryKeys.size) {
            throw SQLException("主键字段数量和传入的参数不匹配")
        }

        val select = select().from(tableName)
        primaryKeys.forEach {
            if (pkCols.containsKey(it.first)) {
                select.where(pkCols[it.first]!! eq it.second)
            }
        }

        return select.find()
    }

    inline fun <reified T> find(primaryKeys: Map<String, Any>): T? {
        return find(primaryKeys.map { it.key to it.value })
    }

    inline fun <reified T> queryAll(): List<T> {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val tableName = (companion as TableSchema).tableName

        val select = select().from(tableName)
        return select.query()
    }

    inline fun <reified T> page(pageSize: Int, pageNumber: Int, needCount: Boolean = true): Page<T> {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val tableName = (companion as TableSchema).tableName

        val select = select().from(tableName)
        return select.page(pageSize, pageNumber, needCount)
    }

    inline fun <reified T> fetchCount(): Long {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val tableName = (companion as TableSchema).tableName

        val select = select().from(tableName)
        return select.fetchCount()
    }

    inline fun <reified T> firstPage(sort: OrderBy, pageSize: Int): List<T> {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val tableName = (companion as TableSchema).tableName

        val select = select().from(tableName)
        return select.firstPage(sort, pageSize)
    }

    inline fun <reified T> lastPage(sort: OrderBy, pageSize: Int): List<T> {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val tableName = (companion as TableSchema).tableName

        val select = select().from(tableName)
        return select.lastPage(sort, pageSize)
    }

    inline fun <reified T> previousPage(sort: OrderBy, value: Any, pageSize: Int): List<T> {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val tableName = (companion as TableSchema).tableName

        val select = select().from(tableName)
        return select.previousPage(sort, value, pageSize)
    }

    inline fun <reified T> nextPage(sort: OrderBy, value: Any, pageSize: Int): List<T> {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val tableName = (companion as TableSchema).tableName

        val select = select().from(tableName)
        return select.nextPage(sort, value, pageSize)
    }

    infix fun update(table: String): Update {
        checkOLAP(this.db)

        val update = Update(db, getConnection(), isTransaction)
        update.update(table)
        return update
    }

    infix fun update(table: TableSchema): Update {
        checkOLAP(this.db)

        val update = Update(db, getConnection(), isTransaction)
        update.update(table)
        return update
    }

    infix fun update(entity: Any): Int {
        checkOLAP(this.db)

        val update = Update(db, getConnection(), isTransaction)
        update.update(entity)
        return update.exec()
    }

    infix fun insert(table: String): NativeInsert {
        checkOLAP(this.db)

        val insert = NativeInsert(db, getConnection(), isTransaction)
        insert.into(table)
        return insert
    }

    infix fun insert(table: TableSchema): Insert {
        checkOLAP(this.db)

        val insert = Insert(db, getConnection(), isTransaction)
        insert.into(table)
        return insert
    }

    infix fun insert(entity: Any): Int {
        checkOLAP(this.db)

        val insert = Insert(db, getConnection(), isTransaction)
        insert.insert(entity)
        return insert.exec()
    }

    infix fun insert(entity: List<Any>): Int {
        checkOLAP(this.db)

        val insert = Insert(db, getConnection(), isTransaction)
        insert.insert(entity)
        return insert.exec()
    }

    infix fun save(entity: Any): Int {
        checkOLAP(this.db)

        val clazz = entity::class

        val companion = clazz.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val table = companion as TableSchema
        val tableName = table.tableName

        val properties = table::class.declaredMemberProperties.map { it.name to it.getter.call(table) }
            .filter { it.second is QueryTableColumn }
            .map { it.first to it.second as QueryTableColumn }

        val primaryColumns = properties.filter { it.second.primaryKey }.map { SqlIdentifierExpr(it.second.column) }
        if (primaryColumns.isEmpty()) {
            throw SQLException("实体类的伴生对象中没有设置主键字段")
        }

        val columns = properties.map { SqlIdentifierExpr(it.second.column) }
        val updateColumns = properties.filter { !it.second.primaryKey }.map { SqlIdentifierExpr(it.second.column) }

        val entityProperties = clazz.declaredMemberProperties.map { it.name to it.getter.call(entity) }.toMap()
        val value = properties.map { getExpr(entityProperties[it.first]) }

        val upsert = SqlUpsert(SqlIdentifierExpr(tableName))
        upsert.columns.addAll(columns)
        upsert.primaryColumns.addAll(primaryColumns)
        upsert.updateColumns.addAll(updateColumns)
        upsert.value.addAll(value)

        val sql = toSqlString(upsert, db)
        return exec(getConnection(), sql)
    }

    infix fun delete(table: String): Delete {
        checkOLAP(this.db)

        val delete = Delete(db, getConnection(), isTransaction)
        delete.from(table)
        return delete
    }

    infix fun delete(table: TableSchema): Delete {
        checkOLAP(this.db)

        val delete = Delete(db, getConnection(), isTransaction)
        delete.from(table)
        return delete
    }

    infix fun delete(entity: Any): Int {
        checkOLAP(this.db)

        val delete = Delete(db, getConnection(), isTransaction)
        delete.delete(entity)
        return delete.exec()
    }

    @JvmName("deleteById")
    inline infix fun <reified T> delete(primaryKey: Any): Int {
        checkOLAP(this.db)

        val delete = Delete(db, getConnection(), isTransaction)
        delete.delete<T>(primaryKey)
        return delete.exec()
    }

    inline fun <reified T> delete(primaryKeys: List<Pair<String, Any>>): Int {
        checkOLAP(this.db)

        val delete = Delete(db, getConnection(), isTransaction)
        delete.delete<T>(primaryKeys)
        return delete.exec()
    }

    inline fun <reified T> delete(primaryKeys: Map<String, Any>): Int {
        checkOLAP(this.db)

        val delete = Delete(db, getConnection(), isTransaction)
        delete.delete<T>(primaryKeys)
        return delete.exec()
    }

    infix fun truncate(table: String): Truncate {
        checkOLAP(this.db)

        val truncate = Truncate(db, getConnection(), isTransaction)
        truncate.truncate(table)
        return truncate
    }

    infix fun truncate(table: TableSchema): Truncate {
        checkOLAP(this.db)

        val truncate = Truncate(db, getConnection(), isTransaction)
        truncate.truncate(table)
        return truncate
    }

    infix fun dropTable(table: String): DropTable {
        val dropTable = DropTable(db, getConnection(), isTransaction)
        dropTable.drop(table)
        return dropTable
    }

    infix fun dropIndex(indexName: String): DropIndex {
        val dropIndex = DropIndex(db, getConnection(), isTransaction)
        dropIndex.drop(indexName)
        return dropIndex
    }

    infix fun createTable(table: String): CreateTable {
        val createTable = CreateTable(db, getConnection(), isTransaction)
        createTable.create(table)
        return createTable
    }

    infix fun createIndex(indexName: String): CreateIndex {
        val createIndex = CreateIndex(db, getConnection(), isTransaction)
        createIndex.create(indexName)
        return createIndex
    }

    infix fun createUniqueIndex(indexName: String): CreateIndex {
        val createIndex = CreateIndex(db, getConnection(), isTransaction)
        createIndex.createUnique(indexName)
        return createIndex
    }
}